<?php
// +----------------------------------------------------------------------
/**
 * 加密解密
 */
function encrypt($string, $operation = 'D', $expiry = 0){
    // 动态密匙长度，相同的明文会生成不同密文就是依靠动态密匙
    $ckey_length = 4;
    // 密匙
    $key = md5(config('salt'));
    // 密匙a会参与加解密
    $keya = md5(substr($key, 0, 16));
    // 密匙b会用来做数据完整性验证
    $keyb = md5(substr($key, 16, 16));
    // 密匙c用于变化生成的密文
    $keyc = $ckey_length ? ($operation == 'D' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';
    // 参与运算的密匙
    $cryptkey = $keya . md5($keya . $keyc);
    $key_length = strlen($cryptkey);
    // 明文，前10位用来保存时间戳，解密时验证数据有效性，10到26位用来保存$keyb(密匙b)，
    // 解密时会通过这个密匙验证数据完整性
    // 如果是解码的话，会从第$ckey_length位开始，因为密文前$ckey_length位保存 动态密匙，以保证解密正确
    $string = $operation == 'D' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0) . substr(md5($string . $keyb), 0, 16) . $string;
    $string_length = strlen($string);
    $result = '';
    $box = range(0, 255);
    $rndkey = array();
    // 产生密匙簿
    for($i = 0; $i <= 255; $i++){
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
    }
    // 用固定的算法，打乱密匙簿，增加随机性，好像很复杂，实际上对并不会增加密文的强度
    for($j = $i = 0; $i < 256; $i++){
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }
    // 核心加解密部分
    for($a = $j = $i = 0; $i < $string_length; $i++){
        $a = ($a + 1) % 256;
        $j = ($j + $box[$a]) % 256;
        $tmp = $box[$a];
        $box[$a] = $box[$j];
        $box[$j] = $tmp;
        // 从密匙簿得出密匙进行异或，再转成字符
        $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
    }
    if($operation == 'D'){
        // 验证数据有效性，请看未加密明文的格式
        if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26) . $keyb), 0, 16)){
            return substr($result, 26);
        }else{
            return '';
        }
    }else{
        // 把动态密匙保存在密文里，这也是为什么同样的明文，生产不同密文后能解密的原因
        // 因为加密后的密文可能是一些特殊字符，复制过程可能会丢失，所以用base64编码
        return $keyc . str_replace('=', '', base64_encode($result));
    }
}

/**
 * 获取客户端IP地址
 */
function get_client_ip(){
    if(isset($_SERVER['HTTP_X_REAL_IP'])){
        // nginx 代理模式下，获取客户端真实IP
        $ip = $_SERVER['HTTP_X_REAL_IP'];
    }elseif(isset($_SERVER['HTTP_CLIENT_IP'])){
        // 客户端的ip
        $ip = $_SERVER['HTTP_CLIENT_IP'];
    }elseif(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
        // 浏览当前页面的用户计算机的网关
        $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
        $pos = array_search('unknown', $arr);
        if(false !== $pos) unset($arr[$pos]);
        $ip = trim($arr[0]);
    }elseif(isset($_SERVER['REMOTE_ADDR'])){
        $ip = $_SERVER['REMOTE_ADDR'];
        // 浏览当前页面的用户计算机的ip地址
    }else{
        $ip = $_SERVER['REMOTE_ADDR'];
    }
    if(empty($ip)) $ip = '0.0.0.0';
    return $ip;
}

/**
 * 图片上传
 */
function img_upload($sub_path, $id, $file_data){
    if(empty($file_data) || empty($sub_path) || intval($id) <= 0) exit(json_encode([
        'status' => 'error',
        'message' => '参数错误'
    ]));
    $temp = explode('.', $file_data["name"]);
    $ext = strtolower(end($temp));
    if(!in_array($ext, explode(',', 'gif,jpeg,jpg,png'))) exit(json_encode([
        'status' => 'error',
        'message' => '格式错误'
    ]));
    $upload_path = config('upload_path');
    if(!is_writable(ROOT_PATH . $upload_path)) exit(json_encode([
        "status" => 'error',
        "message" => '上传目录不可写'
    ]));
    if(file_exists(ROOT_PATH . "{$upload_path}{$sub_path}/tmp_{$id}.{$ext}")) unlink(ROOT_PATH . "{$upload_path}{$sub_path}/tmp_{$id}.{$ext}");
    is_dir(ROOT_PATH . "{$upload_path}{$sub_path}") or mkdir(ROOT_PATH . "{$upload_path}{$sub_path}") or exit(json_encode([
        "status" => 'error',
        "message" => '上传子目录创建失败'
    ]));
    if($file_data["error"] > 0) exit(json_encode([
        "status" => 'error',
        "message" => "上传失败，错误码：{$file_data["error"]}"
    ]));
    $filename = $file_data["tmp_name"];
    list($width, $height) = getimagesize($filename);
    move_uploaded_file($filename, ROOT_PATH . "{$upload_path}{$sub_path}/tmp_{$id}.{$ext}");
    exit(json_encode([
        "status" => 'success',
        "url" => "{$upload_path}{$sub_path}/tmp_{$id}.{$ext}?r=" . time(),
        "width" => $width,
        "height" => $height
    ]));
}

/**
 * 图片拷贝
 */
function img_copy($tmp_url, $img_init_w, $img_init_h, $img_w, $img_h, $offset_x, $offset_y, $crop_w, $crop_h, $rotation){
    if(!preg_match('/^\/public\/upload\/[a-z]+\/tmp_\d+\.\w+\?r=\d+$/', $tmp_url)) exit(json_encode([
        "status" => 'error',
        "message" => '参数错误'
    ]));
    $temp = explode('/', $tmp_url);
    $temp[4] = preg_replace('/\?r=\d+/', '', $temp[4]);
    $tmp_url = implode('/', $temp);
    if(!file_exists(ROOT_PATH . $tmp_url)) exit(json_encode([
        "status" => 'error',
        "message" => '文件丢失'
    ]));
    $temp[4] = substr($temp[4], 4);
    // 获取保存文件名
    $img_url = implode('/', $temp);
    // 获取文件后缀
    $temp = explode('.', $temp[4]);
    $ext = strtolower(end($temp));
    if(!in_array($ext, explode(',', 'gif,jpeg,jpg,png'))) exit(json_encode([
        'status' => 'error',
        'message' => '格式错误'
    ]));
    $tmp_url = ROOT_PATH . $tmp_url;
    $img_source = false;
    switch($ext){
        case 'png':
            $img_source = imagecreatefrompng($tmp_url);
            break;
        case 'jpg':
        case 'jpeg':
            $img_source = imagecreatefromjpeg($tmp_url);
            break;
        case 'gif':
            $img_source = imagecreatefromgif($tmp_url);
            break;
    }
    if(!is_writable(dirname($tmp_url))) exit(json_encode([
        "status" => 'error',
        "message" => '上传目录不可写'
    ]));
    $resized_img = imagecreatetruecolor($img_w, $img_h);
    imagecopyresampled($resized_img, $img_source, 0, 0, 0, 0, $img_w, $img_h, $img_init_w, $img_init_h);
    $rotated_img = imagerotate($resized_img, -$rotation, 0);
    $dx = imagesx($rotated_img) - $img_w;
    $dy = imagesy($rotated_img) - $img_h;
    if($dx < 0) $dx = 0;
    if($dy < 0) $dy = 0;
    $cropped_rotated_img = imagecreatetruecolor($img_w, $img_h);
    imagecolortransparent($cropped_rotated_img, imagecolorallocate($cropped_rotated_img, 0, 0, 0));
    imagecopyresampled($cropped_rotated_img, $rotated_img, 0, 0, $dx / 2, $dy / 2, $img_w, $img_h, $img_w, $img_h);
    $final_img = imagecreatetruecolor($crop_w, $crop_h);
    imagecolortransparent($final_img, imagecolorallocate($final_img, 0, 0, 0));
    imagecopyresampled($final_img, $cropped_rotated_img, 0, 0, $offset_x, $offset_y + 1, $crop_w + 6, $crop_h + 6, $crop_w, $crop_h);
    imagejpeg($final_img, ROOT_PATH . $img_url, 100);
    unlink($tmp_url);
    return $img_url;
}

/**
 * 解析树
 */
function tree_parse($items){
    $result = $tmp = [];
    foreach($items as $v){
        $v['children'] = [];
        $tmp[$v['id']] = $v;
    }
    foreach($tmp as $v){
        if(isset($tmp[$v['pid']])){
            $tmp[$v['pid']]['children'][] = &$tmp[$v['id']];
        }else{
            $result[] = &$tmp[$v['id']];
        }
    }
    return $result;
}

